#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>

#define SERV_PORT 8755
#define MAXLINE 32
#define	MAX(a,b) ((a) > (b) ? (a) : (b))
#define error_exit(msg) \
	do { perror(msg); exit(EXIT_FAILURE); } while (0)

void dg_cli_timeoutopt(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int	n;
	char sendline[MAXLINE], recvline[MAXLINE+1];
	struct timeval tv;

	tv.tv_sec = 3;
	tv.tv_usec = 0;
	setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
		
		n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
		if (n < 0) {
			if (errno == EWOULDBLOCK) {
				fprintf(stderr, "socket timeout\n");
				continue;
			} else
				error_exit("recvfrom error");
		}

		recvline[n] = 0;	/* null terminate */
		fputs(recvline, stdout);
	}
}

static int readable_timeo(int fd, int sec) { // 本函数适用于任何类型的套接字
	fd_set rset;
	struct timeval tv;
	
	FD_ZERO(&rset);
	FD_SET(fd, &rset);
	
	tv.tv_sec = sec;
	tv.tv_usec = 0;
	return (select(fd+1, &rset, NULL, NULL, &tv)); // select等待该描述符变为可读，或者发生超时（返回0）
}

void dg_cli_select(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int n;
	char sendline[MAXLINE], recvline[MAXLINE + 1];

	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		if (readable_timeo(sockfd, 3) == 0) {
			fprintf(stderr, "socket timeout\n");
		} else {
			n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
			recvline[n] = 0; /* null terminate */
			fputs(recvline, stdout);
		}
	}
}

void* Signal(int signo, void (*func)(int)) {
	struct sigaction act, oact;

	act.sa_handler = func; 
	sigemptyset(&act.sa_mask); 
	act.sa_flags = 0;
#ifdef SA_INTERRUPT	
	if (signo == SIGALRM) act.sa_flags |= SA_INTERRUPT;
#endif
#ifdef SA_RESTART 
	if (signo != SIGALRM) act.sa_flags |= SA_RESTART; 
#endif
	if (sigaction(signo, &act, &oact) < 0) 
		return SIG_ERR;
	return oact.sa_handler; // 返回信号的旧行为
}

static void sig_alrm(int signo) {
	return;	/* just interrupt the recvfrom() */
}

void dg_cli_timeout(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int n;
	char sendline[MAXLINE], recvline[MAXLINE+1];

	Signal(SIGALRM, sig_alrm);
	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		alarm(3); // 设置报警时钟
		if ((n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {
			if (errno == EINTR) // SIGALRM中断返回
				fprintf(stderr, "socket timeout\n");
			else
				error_exit("recvfrom error");
		} else {
			alarm(0); // 关闭报警时钟
			recvline[n] = 0;
			fputs(recvline, stdout);
		}
	}
}

void dg_cli_broadcast(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int n;
	const int on = 1;
	char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
	socklen_t len;
	struct sockaddr	*preply_addr;
	
	/* 设置套接字选项、分配服务器地址空间、安装SIGALRM信号处理函数 */
	//setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); // POSIX规范要求发送广播数据报必须设置该选项
	preply_addr = malloc(servlen);
	Signal(SIGALRM, sig_alrm);
	
	/* 发送广播数据报 */
	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
		
		/* 5s内接收所有广播应答 
		 * （如果某一时刻，程序刚好成功执行完recvfrom后，此时alarm触发SIGALRM信号，会导致程序永远阻塞在recvfrom上） */
		alarm(5);
		for ( ; ; ) {
			len = servlen;
			n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
			if (n < 0) {
				if (errno == EINTR)
					break;	/* waited long enough for replies */
				else
					error_exit("recvfrom error");
			} else {
				recvline[n] = 0;
				printf("reply from %s:%d : %s", 
					inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
					ntohs(((struct sockaddr_in *)preply_addr)->sin_port),
					recvline);	
			}
		}
	}
	free(preply_addr);
}

void dg_cli_broadcast_sigmask(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int n;
	const int on = 1;
	char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
	socklen_t len;
	struct sockaddr	*preply_addr;
	sigset_t sigset_alrm;
	
	/* 初始化信号集 */
	sigemptyset(&sigset_alrm);
	sigaddset(&sigset_alrm, SIGALRM);	
	
	/* 设置套接字选项、分配服务器地址空间、安装SIGALRM信号处理函数 */
	setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); // POSIX规范要求发送广播数据报必须设置该选项
	preply_addr = malloc(servlen);
	Signal(SIGALRM, sig_alrm);
	
	/* 发送广播数据报 */
	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
		
		/* 5s内接收所有广播应答 
		 * （如果某一时刻，程序刚好成功执行完recvfrom后未执行sigprocmask，此时触发SIGALRM信号，程序仍会永远阻塞在recvfrom上） */
		alarm(5);
		for ( ; ; ) {
			len = servlen;
			/* 限制SIGALRM信号只能在“解阻塞”与“阻塞”之间执行时被内核递交，但仍存在程序永远阻塞在recvfrom上的可能性 */
			sigprocmask(SIG_UNBLOCK, &sigset_alrm, NULL); // 解阻塞SIGALRM信号
			n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
			sigprocmask(SIG_BLOCK, &sigset_alrm, NULL);	// 阻塞SIGALRM信号		
			if (n < 0) {
				if (errno == EINTR)
					break;	/* waited long enough for replies */
				else
					error_exit("recvfrom error");
			} else {
				recvline[n] = 0;
				printf("reply from %s:%d : %s", 
					inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
					ntohs(((struct sockaddr_in *)preply_addr)->sin_port),
					recvline);	
			}
		}
	}
	free(preply_addr);
}

void dg_cli_broadcast_sigmask_pselect(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int n;
	const int on = 1;
	char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
	socklen_t len;
	struct sockaddr	*preply_addr;
	sigset_t sigset_alrm, sigset_empty;
	fd_set rset;
	
	/* 初始化描述符集和信号集 */
	FD_ZERO(&rset);
	sigemptyset(&sigset_empty);
	sigemptyset(&sigset_alrm);
	sigaddset(&sigset_alrm, SIGALRM);	

	/* 设置套接字选项、分配服务器地址空间、安装SIGALRM信号处理函数 */
	setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); // POSIX规范要求发送广播数据报必须设置该选项
	preply_addr = malloc(servlen);
	Signal(SIGALRM, sig_alrm);

	/* 发送广播数据报 */
	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
		
		/* 5s内接收所有广播应答，设置当前信号掩码中SIGALRM被阻塞 */		
		sigprocmask(SIG_BLOCK, &sigset_alrm, NULL);	// 阻塞SIGALRM信号
		alarm(5);
		for ( ; ; ) {
			FD_SET(sockfd, &rset);
			/* pselect依次执行如下三个操作：设置信号掩码、测试描述符、恢复信号掩码。
			 * 1.设置信号掩码 : sigset_empty是一个没有任何信号被阻塞的信号集，意味着其所有信号都是解阻塞的。
			 * 2.测试描述符 : 等同于select。
			 * 3.恢复信号掩码 : pselect返回之前把进程信号掩码恢复成刚被调用时的值。 */
			n = pselect(sockfd+1, &rset, NULL, NULL, NULL, &sigset_empty);
			if (n < 0) {
				if (errno == EINTR)
					break; /* waited long enough for replies */
				else
					error_exit("pselect error");
			} else if (n != 1)
				error_exit("pselect error");
				
			len = servlen;
			n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
			recvline[n] = 0;
			printf("reply from %s:%d : %s", 
				inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
				ntohs(((struct sockaddr_in *)preply_addr)->sin_port),
				recvline);	
		}
	}
	free(preply_addr);
}

static sigjmp_buf jmpbuf; // 跳转缓冲区

static void sig_alarm_jmp(int signo) {
	siglongjmp(jmpbuf, 1); // （nonlocal goto）跳转回sigsetjmp函数，并且使得sigsetjmp返回siglongjmp的第二个参数
}

void dg_cli_jmp(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int n;
	const int on = 1;
	char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
	socklen_t len;
	struct sockaddr *preply_addr;
 
	setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
	preply_addr = malloc(servlen);
	Signal(SIGALRM, sig_alarm_jmp);

	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		alarm(5);
		for ( ; ; ) {
			if (sigsetjmp(jmpbuf, 1) != 0) // sigsetjmp在建立跳转缓冲区后返回0
				break;
			len = servlen;
			n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
			recvline[n] = 0; /* null terminate */
			printf("reply from %s:%d : %s", 
				inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
				ntohs(((struct sockaddr_in *)preply_addr)->sin_port),
				recvline);	
		}
	}
	free(preply_addr);
}

static int pipefd[2];

static void sig_alarm_pipe(int signo) {
	write(pipefd[1], "", 1); // write one null byte to pipe 
	return;
}

void dg_cli_pipe(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int n, maxfdp1;
	const int on = 1;
	char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
	fd_set rset;
	socklen_t len;
	struct sockaddr *preply_addr;
 
	setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));	
	preply_addr = malloc(servlen);	
	Signal(SIGALRM, sig_alarm_pipe);	
	
	pipe(pipefd); // 创建一个Unix管道，返回两个描述符。pipefd[0]是读入端，pipefd[1]是写出端。
	FD_ZERO(&rset);	
	maxfdp1 = MAX(sockfd, pipefd[0]) + 1;
	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		alarm(5);
		for ( ; ; ) {
			/* 对套接字和管道读入端进行select */
			FD_SET(sockfd, &rset);
			FD_SET(pipefd[0], &rset);
			if ( (n = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
				if (errno == EINTR)
					continue;
				else
					error_exit("select error");
			}

			/* 从套接字读 */
			if (FD_ISSET(sockfd, &rset)) {
				len = servlen;
				n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
				recvline[n] = 0; /* null terminate */
				printf("reply from %s:%d : %s", 
					inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
					ntohs(((struct sockaddr_in *)preply_addr)->sin_port),
					recvline);	
			}

			/* 从管道读 */
			if (FD_ISSET(pipefd[0], &rset)) {
				read(pipefd[0], &n, 1);	/* timer expired */
				break;
			}
		}
	}
	free(preply_addr);
}

void dg_cli(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int	n;
	char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
	socklen_t len;
	struct sockaddr *preply_addr;

	preply_addr = malloc(servlen);
	while (fgets(sendline, MAXLINE, fp) != NULL) {
		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		len = servlen;
		n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
		printf("reply from %s:%d : ", 
			inet_ntop(AF_INET, &((struct sockaddr_in *)preply_addr)->sin_addr, buf, sizeof(buf)),
			ntohs(((struct sockaddr_in *)preply_addr)->sin_port));
		if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) 
			printf("(ignored)");

		recvline[n] = 0;
		fputs(recvline, stdout);
	}
	free(preply_addr);
}

void dg_cli_conn(FILE *fp, int sockfd, const struct sockaddr *pservaddr, socklen_t servlen) {
	int	n;
	char sendline[MAXLINE], recvline[MAXLINE+1], buf[MAXLINE];
	struct sockaddr_in cliaddr;
	socklen_t len;

	connect(sockfd, (struct sockaddr *)pservaddr, servlen);
	
	len = sizeof(cliaddr);
	getsockname(sockfd, (struct sockaddr *)&cliaddr, &len);
	printf("local address %s:%d\n", 
		inet_ntop(AF_INET, &cliaddr.sin_addr, buf, sizeof(buf)),
		ntohs(cliaddr.sin_port));
	
	while (fgets(sendline, MAXLINE, fp) != NULL) {
		write(sockfd, sendline, strlen(sendline));

		n = read(sockfd, recvline, MAXLINE);

		recvline[n] = 0;
		fputs(recvline, stdout);
	}
}

#if 1	
int main(int argc, char **argv) {
	int sockfd;
	struct sockaddr_in servaddr;

	if (argc != 2)
		error_exit("usage: udpcli <IPaddress>");

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	dg_cli(stdin, sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	exit(0);
}
#endif


